package br.ufpe.cin.imlm2.jbeat.soot.throwableset.generation;

import java.util.Iterator;

import soot.FastHierarchy;
import soot.IntegerType;
import soot.Local;
import soot.LongType;
import soot.NullType;
import soot.RefLikeType;
import soot.RefType;
import soot.Scene;
import soot.Type;
import soot.Unit;
import soot.UnknownType;
import soot.Value;
import soot.ValueBox;
import soot.jimple.AddExpr;
import soot.jimple.AndExpr;
import soot.jimple.ArrayRef;
import soot.jimple.AssignStmt;
import soot.jimple.BinopExpr;
import soot.jimple.BreakpointStmt;
import soot.jimple.CastExpr;
import soot.jimple.CaughtExceptionRef;
import soot.jimple.ClassConstant;
import soot.jimple.CmpExpr;
import soot.jimple.CmpgExpr;
import soot.jimple.CmplExpr;
import soot.jimple.DivExpr;
import soot.jimple.DoubleConstant;
import soot.jimple.DynamicInvokeExpr;
import soot.jimple.EnterMonitorStmt;
import soot.jimple.EqExpr;
import soot.jimple.ExitMonitorStmt;
import soot.jimple.FloatConstant;
import soot.jimple.GeExpr;
import soot.jimple.GotoStmt;
import soot.jimple.GtExpr;
import soot.jimple.IdentityStmt;
import soot.jimple.IfStmt;
import soot.jimple.InstanceFieldRef;
import soot.jimple.InstanceInvokeExpr;
import soot.jimple.InstanceOfExpr;
import soot.jimple.IntConstant;
import soot.jimple.InterfaceInvokeExpr;
import soot.jimple.InvokeStmt;
import soot.jimple.JimpleValueSwitch;
import soot.jimple.LeExpr;
import soot.jimple.LengthExpr;
import soot.jimple.LongConstant;
import soot.jimple.LookupSwitchStmt;
import soot.jimple.LtExpr;
import soot.jimple.MulExpr;
import soot.jimple.NeExpr;
import soot.jimple.NegExpr;
import soot.jimple.NewArrayExpr;
import soot.jimple.NewExpr;
import soot.jimple.NewMultiArrayExpr;
import soot.jimple.NopStmt;
import soot.jimple.NullConstant;
import soot.jimple.OrExpr;
import soot.jimple.ParameterRef;
import soot.jimple.RemExpr;
import soot.jimple.RetStmt;
import soot.jimple.ReturnStmt;
import soot.jimple.ReturnVoidStmt;
import soot.jimple.ShlExpr;
import soot.jimple.ShrExpr;
import soot.jimple.SpecialInvokeExpr;
import soot.jimple.StaticFieldRef;
import soot.jimple.StaticInvokeExpr;
import soot.jimple.Stmt;
import soot.jimple.StmtSwitch;
import soot.jimple.StringConstant;
import soot.jimple.SubExpr;
import soot.jimple.TableSwitchStmt;
import soot.jimple.ThisRef;
import soot.jimple.ThrowStmt;
import soot.jimple.UshrExpr;
import soot.jimple.VirtualInvokeExpr;
import soot.jimple.XorExpr;
import soot.toolkits.exceptions.ThrowableSet;
import soot.toolkits.exceptions.UnitThrowAnalysis;
import br.ufpe.cin.imlm2.jbeat.soot.common.CommonExceptions;

public class SimpleStmtThrowAnalysis extends UnitThrowAnalysis {

	private final IntConstant				INT_CONSTANT_ZERO	= IntConstant.v( 0 );
	private final LongConstant				LONG_CONSTANT_ZERO	= LongConstant.v( 0 );

	private static SimpleStmtThrowAnalysis	INSTANCE;

	private SimpleStmtThrowAnalysis() {
		// Shouldn't be explicitly instantiated.
	}

	public static final SimpleStmtThrowAnalysis getInstance() {
		if ( INSTANCE == null )
			INSTANCE = new SimpleStmtThrowAnalysis();
		return INSTANCE;
	}

	@Override
	public ThrowableSet mightThrow( Unit u ) {
		if ( u instanceof Stmt )
			return this.stmtMightThrow( (Stmt) u );
		else
			return super.mightThrow( u );
	}

	protected ThrowableSet valueMightThrow( Value v ) {
		ValueSwitchImpl vsi = new ValueSwitchImpl();
		v.apply( vsi );
		return vsi.getResult();
	}

	protected ThrowableSet stmtMightThrow( Stmt stmt ) {
		StmtSwitchImpl ssi = new StmtSwitchImpl();
		stmt.apply( ssi );
		return ssi.getResult();
	}

	protected class ValueSwitchImpl implements JimpleValueSwitch {

		private final ThrowableSet.Manager	mgr		= ThrowableSet.Manager.v();
		private ThrowableSet				result	= mgr.EMPTY;

		protected ThrowableSet getResult() {
			return result;
		}

		// Declared by ConstantSwitch interface:

		public void caseDoubleConstant( DoubleConstant c ) {
			// Empty.
		}

		public void caseFloatConstant( FloatConstant c ) {
			// Empty.
		}

		public void caseIntConstant( IntConstant c ) {
			// Empty.
		}

		public void caseLongConstant( LongConstant c ) {
			// Empty.
		}

		public void caseNullConstant( NullConstant c ) {
			// Empty.
		}

		public void caseStringConstant( StringConstant c ) {
			// Empty.
		}

		public void caseClassConstant( ClassConstant c ) {
			// Empty.
		}

		// Declared by ExprSwitch interface:

		public void caseAddExpr( AddExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseAndExpr( AndExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseCmpExpr( CmpExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseCmpgExpr( CmpgExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseCmplExpr( CmplExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseDivExpr( DivExpr expr ) {
			caseBinopDivExpr( expr );
		}

		public void caseEqExpr( EqExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseNeExpr( NeExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseGeExpr( GeExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseGtExpr( GtExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseLeExpr( LeExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseLtExpr( LtExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseMulExpr( MulExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseOrExpr( OrExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseRemExpr( RemExpr expr ) {
			caseBinopDivExpr( expr );
		}

		public void caseShlExpr( ShlExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseShrExpr( ShrExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseUshrExpr( UshrExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseSubExpr( SubExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseXorExpr( XorExpr expr ) {
			caseBinopExpr( expr );
		}

		public void caseInterfaceInvokeExpr( InterfaceInvokeExpr expr ) {
			caseInstanceInvokeExpr( expr );
		}

		public void caseSpecialInvokeExpr( SpecialInvokeExpr expr ) {
			caseInstanceInvokeExpr( expr );
		}

		public void caseStaticInvokeExpr( StaticInvokeExpr expr ) {
			for ( int i = 0; i < expr.getArgCount(); i++ ) {
				result = result.add( valueMightThrow( (expr.getArg( i )) ) );
			}
		}

		public void caseVirtualInvokeExpr( VirtualInvokeExpr expr ) {
			caseInstanceInvokeExpr( expr );
		}

		
		// Java 7 JVM instruction.
		public void caseDynamicInvokeExpr( DynamicInvokeExpr expr ) {
			// Empty.
		}

		public void caseCastExpr( CastExpr expr ) {
			Type fromType = expr.getOp().getType();
			Type toType = expr.getCastType();
			if ( toType instanceof RefLikeType ) {
				// fromType might still be unknown when we are called,
				// but toType will have a value.
				FastHierarchy h = Scene.v().getOrMakeFastHierarchy();
				if ( fromType == null
						|| fromType instanceof UnknownType
						|| ((!(fromType instanceof NullType)) && (!h
								.canStoreType( fromType, toType ))) ) {
					result = result.add( CommonExceptions.getCLASSCAST().getType() );
				}
			}
			result = result.add( valueMightThrow( expr.getOp() ) );
		}

		public void caseInstanceOfExpr( InstanceOfExpr expr ) {
			result = result.add( valueMightThrow( expr.getOp() ) );
		}

		public void caseNewArrayExpr( NewArrayExpr expr ) {
			Value count = expr.getSize();
			if ( (!(count instanceof IntConstant))
					|| (((IntConstant) count).lessThan( INT_CONSTANT_ZERO )
							.equals( INT_CONSTANT_ZERO )) ) {
				result = result.add( CommonExceptions.getNEGATIVEARRAYSIZE().getType() );
			}
			result = result.add( valueMightThrow( count ) );
		}

		public void caseNewMultiArrayExpr( NewMultiArrayExpr expr ) {
			for ( int i = 0; i < expr.getSizeCount(); i++ ) {
				Value count = expr.getSize( i );
				if ( (!(count instanceof IntConstant))
						|| (((IntConstant) count).lessThan( INT_CONSTANT_ZERO )
								.equals( INT_CONSTANT_ZERO )) ) {
					result = result.add( CommonExceptions.getNEGATIVEARRAYSIZE().getType() );
				}
				result = result.add( valueMightThrow( count ) );
			}
		}

		@SuppressWarnings("unchecked")
		public void caseNewExpr( NewExpr expr ) {
			for ( Iterator<ValueBox> i = expr.getUseBoxes().iterator(); i.hasNext(); ) {
				ValueBox box = (ValueBox) i.next();
				result = result.add( valueMightThrow( box.getValue() ) );
			}
		}

		public void caseLengthExpr( LengthExpr expr ) {
			result = result.add( CommonExceptions.getNULLPOINTER().getType() );
			result = result.add( valueMightThrow( expr.getOp() ) );
		}

		public void caseNegExpr( NegExpr expr ) {
			result = result.add( valueMightThrow( expr.getOp() ) );
		}

		// Declared by RefSwitch interface:

		public void caseArrayRef( ArrayRef ref ) {
			result = result.add( CommonExceptions.getNULLPOINTER().getType() );
			result = result.add( CommonExceptions.getARRAYINDEXOUTOFBOUNDS().getType() );
			result = result.add( valueMightThrow( ref.getBase() ) );
			result = result.add( valueMightThrow( ref.getIndex() ) );
		}

		public void caseStaticFieldRef( StaticFieldRef ref ) {
			// Empty.
		}

		public void caseInstanceFieldRef( InstanceFieldRef ref ) {
			result = result.add( CommonExceptions.getNULLPOINTER().getType() );
			result = result.add( valueMightThrow( ref.getBase() ) );
		}

		public void caseParameterRef( ParameterRef v ) {
			// Empty.
		}

		public void caseCaughtExceptionRef( CaughtExceptionRef v ) {
			// Empty.
		}

		public void caseThisRef( ThisRef v ) {
			// Empty.
		}

		public void caseLocal( Local l ) {
			// Empty.
		}

		public void defaultCase( Object obj ) {
			// Empty.
		}

		protected void caseBinopExpr( BinopExpr expr ) {
			result = result.add( valueMightThrow( expr.getOp1() ) );
			result = result.add( valueMightThrow( expr.getOp2() ) );
		}

		protected void caseBinopDivExpr( BinopExpr expr ) {
			// Factors out code common to caseDivExpr and caseRemExpr.
			// The checks against constant divisors would perhaps be
			// better performed in a later pass, post-constant-propagation.
			Value divisor = expr.getOp2();
			Type divisorType = divisor.getType();
			if ( divisorType instanceof UnknownType ) {
				result = result.add( CommonExceptions.getARITHMETIC().getType() );
			} else if ( (divisorType instanceof IntegerType)
					&& ((!(divisor instanceof IntConstant)) || (((IntConstant) divisor)
							.equals( INT_CONSTANT_ZERO ))) ) {
				result = result.add( CommonExceptions.getARITHMETIC().getType() );
			} else if ( (divisorType == LongType.v())
					&& ((!(divisor instanceof LongConstant)) || (((LongConstant) divisor)
							.equals( LONG_CONSTANT_ZERO ))) ) {
				result = result.add( CommonExceptions.getARITHMETIC().getType() );
			}
			caseBinopExpr( expr );
		}

		protected void caseInstanceInvokeExpr( InstanceInvokeExpr expr ) {
			result = result.add( CommonExceptions.getNULLPOINTER().getType() );
			for ( int i = 0; i < expr.getArgCount(); i++ ) {
				result = result.add( valueMightThrow( expr.getArg( i ) ) );
			}
			result = result.add( valueMightThrow( expr.getBase() ) );
		}
	}

	protected class StmtSwitchImpl implements StmtSwitch {

		private final ThrowableSet.Manager	mgr		= ThrowableSet.Manager.v();

		private ThrowableSet				result	= mgr.EMPTY;

		protected ThrowableSet getResult() {
			return result;
		}

		public void caseAssignStmt( AssignStmt s ) {
			Value lhs = s.getLeftOp();
			if ( lhs instanceof ArrayRef
					&& (lhs.getType() instanceof UnknownType || lhs.getType() instanceof RefType) ) {
				// This corresponds to an aastore byte code.
				result = result.add( CommonExceptions.getARRAYSTORE().getType() );
			}
			result = result.add( valueMightThrow( s.getLeftOp() ) );
			result = result.add( valueMightThrow( s.getRightOp() ) );
		}

		public void caseBreakpointStmt( BreakpointStmt s ) {
		}

		public void caseEnterMonitorStmt( EnterMonitorStmt s ) {
			result = result.add( CommonExceptions.getNULLPOINTER().getType() );
			result = result.add( valueMightThrow( s.getOp() ) );
		}

		public void caseExitMonitorStmt( ExitMonitorStmt s ) {
			// Consider IllegalMonitorStateException ?
			// result = result.add(mgr.ILLEGAL_MONITOR_STATE_EXCEPTION);
			result = result.add( CommonExceptions.getNULLPOINTER().getType() );
			result = result.add( valueMightThrow( s.getOp() ) );
		}

		public void caseGotoStmt( GotoStmt s ) {
			// Empty.
		}

		public void caseIdentityStmt( IdentityStmt s ) {
			// Empty.
		}

		public void caseIfStmt( IfStmt s ) {
			result = result.add( valueMightThrow( s.getCondition() ) );
		}

		public void caseInvokeStmt( InvokeStmt s ) {
			result = result.add( valueMightThrow( s.getInvokeExpr() ) );
		}

		public void caseLookupSwitchStmt( LookupSwitchStmt s ) {
			result = result.add( valueMightThrow( s.getKey() ) );
		}

		public void caseNopStmt( NopStmt s ) {
			// Empty.
		}

		public void caseRetStmt( RetStmt s ) {
			// Empty.
		}

		public void caseReturnStmt( ReturnStmt s ) {
			// Consider IllegalMonitorStateException ?
			// result = result.add(mgr.ILLEGAL_MONITOR_STATE_EXCEPTION);
			result = result.add( valueMightThrow( s.getOp() ) );
		}

		public void caseReturnVoidStmt( ReturnVoidStmt s ) {
			// Consider IllegalMonitorStateException ?
			// result = result.add(mgr.ILLEGAL_MONITOR_STATE_EXCEPTION);
		}

		public void caseTableSwitchStmt( TableSwitchStmt s ) {
			result = result.add( valueMightThrow( s.getKey() ) );
		}

		public void caseThrowStmt( ThrowStmt s ) {
			result = mightThrowImplicitly( s );
			result = result.add( mightThrowExplicitly( s ) );
		}

		public void defaultCase( Object obj ) {
			// Empty.
		}
	}

}
